﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Xna.Framework;
using UberChess;
using UberChess.Evaluation;
using UberChess.StateGenerator;
using UberChess.Structure.Move;
using UberChess.Structure.Pieces;

namespace UberChess.MinMaxAlgorithm
{
    public enum MinMaxType
    {
        Minimizer,
        Maximizer
    } ;

    [Serializable]
    public class MinMax
    {
        public int CurrentSearchLevel;
        private bool _isFirstMove;
        private IPiece exchangedPiece;
        private List<MinMax> _childNodes;
        private MinMaxType _type;
        private Side _playerside;
        private StateGenerator.StateGenerator stateGenerator;
        private EvaluationValues staticEvaluator;
        public PossibleMove Move { get; set; }
        public double Heuristic { get; private set; }
        public State CurrentState { get; private set; }

        public MinMax(State currentState, int currentSearchLevel, MinMaxType type, Side playerSide)
        {
            Initialize(currentState, currentSearchLevel, type, playerSide);
        }

        public MinMax(State currentState, int currentSearchLevel, MinMaxType type, Side playerSide, PossibleMove move)
        {
            Move = move;
            Initialize(currentState, currentSearchLevel, type, playerSide);
        }

        private void Initialize(State currentState, int currentSearchLevel, MinMaxType type, Side playerSide)
        {
            CurrentState = currentState;
            CurrentSearchLevel = currentSearchLevel;
            _playerside = playerSide;

            stateGenerator = new StateGenerator.StateGenerator();
            staticEvaluator = new EvaluationValues();

            _type = type;
            _childNodes = new List<MinMax>();
            Heuristic = (_type == MinMaxType.Minimizer ? Double.PositiveInfinity : Double.NegativeInfinity);
        }

        private double Search(double alpha, double beta)
        {
            if(Move != null)
                PerformMove(Move);

            if (CurrentState.CheckForCheckMate(_playerside)) //Playerside
            {
                Heuristic = 100000 * CurrentSearchLevel;
                RevertMove(Move);
                return Heuristic;
            }

            if (CurrentState.CheckForCheckMate((_playerside == Side.WHITE ? Side.BLACK : Side.WHITE))) //Computerside
            {
                Heuristic = -100000 * CurrentSearchLevel;
                RevertMove(Move);
                return Heuristic;
            }

            if (CurrentState.CheckForDraw())
            {
                Heuristic = 0.0;
                RevertMove(Move);
                return Heuristic;
            }

            if (CurrentSearchLevel == 0)
            {
                var staticEvaluator = new EvaluationValues();
                Heuristic = staticEvaluator.EvaluateBoard(CurrentState, _playerside);
                RevertMove(Move);
                return Heuristic;
            }

            _childNodes.AddRange(PopulateChildNodes());

            foreach (var child in _childNodes)
            {
                double V;

                if (alpha < beta)
                {
                    V = child.Search(alpha, beta);
                    if (_type == MinMaxType.Minimizer)
                    {
                        if (V < beta)
                        {
                            beta = Heuristic = V;
                        }
                    }
                    else
                    {
                        if (V > alpha)
                        {
                            alpha = Heuristic = V;
                        }
                    }
                }
                else
                {
                    break;
                }
            }

            if(Move != null)
                RevertMove(Move);

            return Heuristic;
        }

        public void AddNode(MinMax node)
        {
            _childNodes.Insert(0, node);
        }

        private List<MinMax> PopulateChildNodes()
        {
            var pieces = CurrentState.GetPieces().Where(
                    piece => (_type == MinMaxType.Minimizer ? piece.GetSide() == (_playerside == Side.WHITE ? Side.BLACK : Side.WHITE)
                         : piece.GetSide() == _playerside)).ToList();

            var possibleMoves = new List<PossibleMove>();
            pieces.ForEach(piece => possibleMoves.AddRange(piece.GetPossibleMoves(CurrentState)));
            possibleMoves = possibleMoves.Where(move => CurrentState.GetPieceAtPosition(move.possibleMove).GetType() != typeof (King)).ToList();

            var evaluatedMoves = possibleMoves.Select(PreliminaryEvaluation).ToList();
            evaluatedMoves = evaluatedMoves.Where(move => !move.IsInCheck).ToList();
            evaluatedMoves = _type == MinMaxType.Minimizer
                                    ? evaluatedMoves.OrderBy(move => move.EvaluationResult).ToList()
                                    : evaluatedMoves.OrderByDescending(move => move.EvaluationResult).ToList();

            return evaluatedMoves.Select(move => new MinMax(CurrentState, CurrentSearchLevel - 1, _type == MinMaxType.Minimizer ? MinMaxType.Maximizer : MinMaxType.Minimizer, _playerside, move.Move)).ToList();
        }

        private PreliminaryResult PreliminaryEvaluation(PossibleMove move)
        {
            var activeSide = (_type == MinMaxType.Minimizer ? _playerside == Side.WHITE ? Side.BLACK : Side.WHITE : _playerside);
            PerformMove(move);
            var inCheck = CurrentState.CheckForCheck(activeSide);
            var value = staticEvaluator.EvaluateBoard(CurrentState, _playerside);
            RevertMove(move);

            return new PreliminaryResult(value, inCheck, move);
        }

        private void PerformMove(PossibleMove move)
        {
            var piece = CurrentState.GetPieceAtPosition(move.currentPosition);
            var activeSide = (_type == MinMaxType.Maximizer ? (_playerside == Side.WHITE ? Side.BLACK : Side.WHITE) : _playerside);
            piece.CurrentPosition = move.possibleMove;

            if(move.isCaptureMove)
            {
                CurrentState.capturedPieces.Push(CurrentState.GetPieceAtPosition(move.possibleMove));
            }
            CurrentState.state[(int)move.possibleMove.X, (int)move.possibleMove.Y] = piece;
            CurrentState.state[(int)move.currentPosition.X, (int)move.currentPosition.Y] = new Empty();

            ChangeHasMoved(piece);

            if(CurrentState.IsExchangePossible(activeSide))
            {
                exchangedPiece = piece;
                CurrentState.Exchange(ExchangeType.Queen, activeSide);
            }

            for(int i = 0; i < 8; i++)
                for(int j = 0; j < 8; j++)
                {
                    var p = CurrentState.GetPieceAtPosition(new Vector2(i, j));
                    if (p.GetType() != typeof(Empty))
                    {
                        if (p.CurrentPosition.X != i || p.CurrentPosition.Y != j)
                        {

                        }
                    }
                }
        }

        private void RevertMove(PossibleMove move)
        {
            IPiece piece;
            piece = exchangedPiece ?? CurrentState.GetPieceAtPosition(move.possibleMove);
            piece.CurrentPosition = move.currentPosition;
            CurrentState.state[(int)piece.CurrentPosition.X, (int)piece.CurrentPosition.Y] = piece;

            RevertHasMoved(piece);

            if(move.isCaptureMove)
            {
                var capturedPiece = CurrentState.capturedPieces.Pop();
                CurrentState.state[(int) capturedPiece.CurrentPosition.X, (int) capturedPiece.CurrentPosition.Y] = capturedPiece;
            }
            else
            {
                CurrentState.state[(int)move.possibleMove.X, (int)move.possibleMove.Y] = new Empty();
            }
        }

        private void ChangeHasMoved(IPiece piece)
        {
            if (piece.GetType() == typeof(Pawn))
            {
                if (!((Pawn)piece).hasMoved)
                {
                    ((Pawn)piece).hasMoved = true;
                    _isFirstMove = true;
                }
            }

            else if (piece.GetType() == typeof(King))
            {
                if (!((King)piece).hasMoved)
                {
                    ((King)piece).hasMoved = true;
                    _isFirstMove = true;
                }
            }

            else if (piece.GetType() == typeof(Rook))
            {
                if (!((Rook)piece).hasMoved)
                {
                    ((Rook)piece).hasMoved = true;
                    _isFirstMove = true;
                }
            }
        }

        private void RevertHasMoved(IPiece piece)
        {
            if (piece.GetType() == typeof (Pawn) && _isFirstMove)
            {
                ((Pawn) piece).hasMoved = false;
            }

            else if (piece.GetType() == typeof (King) && _isFirstMove)
            {
                ((King) piece).hasMoved = false;
            }

            else if (piece.GetType() == typeof (Rook) && _isFirstMove)
                ((Rook) piece).hasMoved = false;
        }

        public MinMax NextMove()
        {
            var _heuristic = Search(double.NegativeInfinity, double.PositiveInfinity);
            var nodes = _childNodes.Where(e => e.Heuristic ==_heuristic);
            return nodes.First();
        }
    }
}